import React from 'react'
import {
  Action,
  UpdaterFn,
  ActionName,
  ActionResult,
  PanelComponentProps,
  ActionSource
} from './types'
import { ExcalidrawElement } from '../element/types'
import { AppClassProperties, AppState } from '../types'
import { trackEvent } from '../analytics'
import { globalEventEmitter } from '/imports/utils'
import { EVENT } from '../constants'

const trackAction = (
  action: Action,
  source: ActionSource,
  appState: Readonly<AppState>,
  elements: readonly ExcalidrawElement[],
  app: AppClassProperties,
  value: any
) => {
  if (action.trackEvent) {
    try {
      if (typeof action.trackEvent === 'object') {
        const shouldTrack = action.trackEvent.predicate
          ? action.trackEvent.predicate(appState, elements, value)
          : true
        if (shouldTrack) {
          trackEvent(
            action.trackEvent.category,
            action.trackEvent.action || action.name,
            `${source} (${app.device.isMobile ? 'mobile' : 'desktop'})`
          )
        }
      }
    } catch (error) {
      console.error('error while logging action:', error)
    }
  }
}

export class ActionManager {
  actions = {} as Record<ActionName, Action>

  updater: (actionResult: ActionResult | Promise<ActionResult>) => void

  getAppState: () => Readonly<AppState>
  getElementsIncludingDeleted: () => readonly ExcalidrawElement[]
  app: AppClassProperties

  constructor(
    updater: UpdaterFn,
    getAppState: () => AppState,
    getElementsIncludingDeleted: () => readonly ExcalidrawElement[],
    app: AppClassProperties
  ) {
    this.updater = actionResult => {
      if (actionResult && 'then' in actionResult) {
        actionResult.then(actionResult => {
          return updater(actionResult)
        })
      } else {
        return updater(actionResult)
      }
    }
    this.getAppState = getAppState
    this.getElementsIncludingDeleted = getElementsIncludingDeleted
    this.app = app
  }

  registerAction(action: Action) {
    this.actions[action.name] = action
  }

  registerAll(actions: readonly Action[]) {
    actions.forEach(action => this.registerAction(action))
  }

  handleKeyDown(event: React.KeyboardEvent | KeyboardEvent) {
    globalEventEmitter.emit(EVENT.KEYDOWN, event)
    const canvasActions = this.app.props.UIOptions.canvasActions
    const data = Object.values(this.actions)
      .sort((a, b) => (b.keyPriority || 0) - (a.keyPriority || 0))
      .filter(
        action =>
          (action.name in canvasActions
            ? canvasActions[action.name as keyof typeof canvasActions]
            : true) &&
          action.keyTest &&
          action.keyTest(event, this.getAppState(), this.getElementsIncludingDeleted())
      )

    if (data.length !== 1) {
      if (data.length > 1) {
        console.warn('Canceling as multiple actions match this shortcut', data)
      }
      return false
    }

    const action = data[0]

    if (this.getAppState().viewModeEnabled && action.viewMode !== true) {
      return false
    }

    const elements = this.getElementsIncludingDeleted()
    const appState = this.getAppState()
    const value = null

    trackAction(action, 'keyboard', appState, elements, this.app, null)

    event.preventDefault()
    event.stopPropagation()
    this.updater(data[0].perform(elements, appState, value, this.app))
    return true
  }

  executeAction(action: Action, source: ActionSource = 'api') {
    const elements = this.getElementsIncludingDeleted()
    const appState = this.getAppState()
    const value = null

    trackAction(action, source, appState, elements, this.app, value)

    this.updater(action.perform(elements, appState, value, this.app))
  }

  /**
   * @param data additional data sent to the PanelComponent
   */
  public renderAction = (
    name: ActionName,
    data?: PanelComponentProps['data'],
    isInHamburgerMenu = false
  ) => {
    const canvasActions = this.app.props.UIOptions.canvasActions

    if (
      this.actions[name] &&
      'PanelComponent' in this.actions[name] &&
      (name in canvasActions ? canvasActions[name as keyof typeof canvasActions] : true)
    ) {
      const action = this.actions[name]
      const PanelComponent = action.PanelComponent!
      PanelComponent.displayName = 'PanelComponent'
      const elements = this.getElementsIncludingDeleted()
      const appState = this.getAppState()
      const updateData = (formState?: any) => {
        trackAction(action, 'ui', appState, elements, this.app, formState)

        this.updater(
          action.perform(
            this.getElementsIncludingDeleted(),
            this.getAppState(),
            formState,
            this.app
          )
        )
      }

      return (
        <PanelComponent
          elements={this.getElementsIncludingDeleted()}
          appState={this.getAppState()}
          updateData={updateData}
          appProps={this.app.props}
          data={data}
          isInHamburgerMenu={isInHamburgerMenu}
        />
      )
    }

    return null
  }
}
